<template>
  <div ref="graphContent" class="g-relative v-graph-content">
    <m-top-bar :ui="ui" :graphContent="graphContent"/>
    <div class="g-flex g-flex-1 g-relative _graph-out" :style="{height:$globalConfig.graphHeight}">
      <m-sider-bar ref="siderBar" :diagramType="diagramType" :graph="graph" class="g-flex-0"/>
      <div class="g-relative g-flex-1  _graph-in" style="height:100%; background-color: #fff">
        <div
          ref="graphDom"
          :style="{width:'100%',height:'100%'}"
          class="g-relative graphDom"
          @drop="handleTreeDrop"
          @dragover="handleTreeDragMouseover">

        </div>
        <m-popmenu
          :x="popMenu.x"
          :y="popMenu.y"
          :visible="popMenu.visible"
          :menuItems="popMenu.menuItems"
          @click-btn="handlePopMenuClick"
          @mouseenter.native="handlePopmenuMouseEnter"
        />
      </div>
      <m-outline ref="outline" :graph="graph" />
    </div>
  </div>
</template>

<script>
import { ContextMenu } from 'components/common/ContextMenu/index';
import mxgraph from './graph/index';
import EditorUi from './graph/EditorUi';
import Editor from './graph/Editor';
import grapheditorTxt from './resources/grapheditor.txt';
import defaultXml from './styles/default.xml';
import { Graph } from './graph/Graph';
import './protoReset';
import treeUtil from 'src/components/ModelTree/treeUtil';
import graphUtil from "pages/mxgraph/graphUtil";
import freshUtil from 'pages/mxgraph/freshUtil';
import OutLine from 'components/Graph/comps/outline/outline';
import TopBar from "components/Graph/comps/topBar/topBar";
import SiderBar from "components/Graph/comps/siderBar/siderBar";
import PopMenu from "components/Graph/comps/popMenu/popMenu";
import { shapeApi } from 'src/api/shape/shapeApi';
import resizeUtil from './resizeUtil';
import { behaviors } from './behaviors/index';
import { debounce } from 'lodash';
import { propertyMenuUtil } from './PropertyMenuUtil';
import { actions } from './Actions';
import './KeyboardHandler';
import { graphHelper } from '@/api/GraphHelper';
import { GraphNodeUtil } from './GraphNodeUtil';
import { PropertyUtil } from 'src/components/Attribute/comps/PropertyUtil.ts';
import { GraphShapeUtil } from './GraphShapeUtil';
import { selectValue } from '@/components/Attribute/modals/SelectValue.vue';
import { controller } from '@/components/Simulation/engine/Controller';
import { treeForEach } from '@/util/common';
import { GraphTabUtil } from '@/components/GraphTabs/GraphTabUtil';
import { GraphNodeType } from '@/constants/Enums';
import { dumpLog } from '@/constants/ErrorConfig';

const {
  mxGraph,
  mxEvent,
  mxUtils,
  mxResources,
  mxCellHighlight
} = mxgraph;

export default {
  name: 'grapheditor',
  components: {
    'm-outline': OutLine,
    'm-top-bar': TopBar,
    'm-sider-bar': SiderBar,
    'm-popmenu': PopMenu

  },
  props: {

    diagramNode: Object,
    visible: Boolean,
    diagramType: String
  },
  data() {
    const data = {
      ui: null, //// 存储mEditorUi实例
      typeProperty: null, //// 存储选中元素的type
      events: {
        'fresh-graph': this.handleFreshGraph,
        'fresh-graph-debounce': debounce( this.handleFreshGraph, 1),  //// 带防抖的刷新，防止多画布时其他画布被防抖
        'select-cell': this.handleSelectCell,
        'remove-graph-highlight': this.handleRemoveHightlight,
        'update-shape-auto-flow': this.updateShapeAutoFlow

      },
      popMenu: {
        x: 0,
        y: 0,
        visible: false,
        closeTimer: undefined,
        cellId: undefined,
        menuItems: []

      },
      vertexBtn: {
        btns: [],
        parentCellId: undefined
      },
      simulateHighLightCells: new Map(), //// key: nodeId, value: highlights
      graphContent: null, //// 用于传递当前画布dom
      updateAutoFlowTimers: {}
    };
    if ( this.diagramType === 'SysMLActivityDiagram' || this.diagramType === 'SysMLStateMachineDiagram'){
      data.events['simulate-step'] = this.handleSimulateStep;
      data.events['simulate-init'] = this.handleSimulateInit;

    }
    if (this.diagramType === 'SysMLActivityDiagram' || this.diagramType === 'SysMLStateMachineDiagram'){
      data.events['change-node-color'] = this.handleChangeNodeColor;
      data.events['simulate-init'] = this.handleSimulateInit;
    }
    return data;
  },
  computed: {
    graph() {
      return this.ui && this.ui.editor.graph;
    },
    graphModel() {
      return this.graph && this.graph.model;
    },

    isElectron() {
      return app.$globalConfig.isElectron;
    },
    diagramShape(){
      return this.diagramNode.umlDiagramFrame;
    }

  },
  watch: {

    visible(bool) {
      this.graph.visible = bool;
      
      console.log( this.diagramNode.title, this.graph.visible, '--------------this.graph.visible');
      if (bool){

        this.handleFreshGraph();
      }

    }

  },
  mounted() {
    this.graphContent = this.$refs.graphContent;
    this.$bus.onBatch(this.events);

    this.init();

  },
  beforeDestroy() {
    this.$bus.offBatch(this.events);
    Object.values(this.updateAutoFlowTimers).forEach(val => {
      clearTimeout(val);
    });
    this.clearVertexBtn();
    if ( app.ui) {
      const tabIndex = app.ui.graphTabs.findIndex(tab => tab.id === this.editorId);
      if (tabIndex > -1) app.ui.graphTabs.splice(tabIndex, 1);
    }
    //// debugger;
    if (this.$refs.outline && this.$refs.outline.outline) {
      this.$refs.outline.outline.destroy();
    }
    //// treeForEach([this.graph.model.cells[this.diagramShape.id]], node => {
    ////   node.umlShape = null;
    //// });
    Object.values(this.graph.model.cells).forEach(cell => {
      cell.umlShape = null;
    });
    //// 释放资源引用，解决内存泄漏问题
    this.ui.editor.graph.rubberband = null;
    this.ui.editor.graph.firstClickState = null;
    this.ui.editor.graph.tapAndHoldSelection = null;
    this.ui.editor.graph.diagramType = null;
    this.ui.editor.graph.diagramNode = null;
    this.ui.editor.graph.diagramShape = null;
    this.ui.editor.graph.behaviors = null;
    this.ui.editor.graph.destroy();

    this.ui.destroy();
    console.log('destroyed---------------------------');
  },
  methods: {
    init() {
      const { model: diagram } = this.diagramNode;

      mxResources.loadDefaultBundle = false;
      mxResources.parse(grapheditorTxt);
      //// Configures the default graph theme
      const themes = {};
      themes[Graph.prototype.defaultThemeName] = mxUtils.parseXml(
        defaultXml
      ).documentElement;
      //// Main
      const mEditorUi = new EditorUi(
        new Editor(null, themes),
        this.$refs.graphDom,
        null,
      );

      const tab = app.ui.graphTabs.find(tab => tab.id === this.diagramNode.id);
      if (!tab) {
        console.error('not found tab', this.diagramNode);
      } else {
        tab.graph = mEditorUi.editor.graph;

      }

      mEditorUi.graphEditor = this;
      mEditorUi.editor.graphEditor = this;
      mEditorUi.editor.graph.graphEditor = this;
      mEditorUi.editor.graph.diagramType = this.diagramType;
      mEditorUi.editor.graph.diagramNode = this.diagramNode;
      mEditorUi.editor.graph.diagramShape = this.diagramShape;
      mEditorUi.editor.graph.behaviors = behaviors;

      const mGraph = mEditorUi.editor.graph;

      this.ui = mEditorUi;

      //// 支持html渲染redrawHandles
      mGraph.setHtmlLabels(true);

      mGraph.zoomTo(1);
      if (this.diagramNode.disabeld === false){
        mGraph.enabled = false;
      }
      //// 自定义的字段
      mGraph.highlight = new mxCellHighlight(mGraph, '#00ff00', 4, false);
      mGraph.highlight.validColor = "#00ff00";
      mGraph.highlight.invalidColor = "#dd0000";

      mGraph.visible = this.visible;
      const curTab = app.graphTabs.find(tab => tab.id === this.diagramNode.id);
      curTab.graph = mGraph;
      mEditorUi.resetScrollbars();
      this.addListener();

      this.initCells();

    },
    initCells() {
      const diagramNode = this.graph.diagramNode;
      console.log(diagramNode);
      freshUtil.updateDiagramName(this.graph);
      const diagramShape = this.diagramShape;
      resizeUtil.initShape(diagramShape);

      graphUtil.addCellByUmlShape(this.graph, diagramShape, { sortEdge: true }, 0);

    },
    addListener() {
      const graph = this.ui.editor.graph;
      const instance = this;
      graph.addListener(mxEvent.CLICK, this.handleGraphClick);
      graph.addListener(mxEvent.DOUBLE_CLICK, this.handleGraphDoubleClick);
      graph.addMouseListener({
        mouseDown() {
          if (app.activeGraphTab.id === instance.diagramNode.id) return;
          GraphTabUtil.changeTab(instance.diagramNode.id);
        }, //// 必须实现的方法
        mouseMove(graph, evt) {
          instance.handleMouseMovePopMenuVisiable(graph, evt);
          instance.handleSiderBarHighlight(graph, evt);

        },
        mouseUp() {
        } //// 必须实现的方法
      });
      graph.addListener(mxEvent.LABEL_CHANGED, this.handleLabelChange);
      //// graph.addListener(mxEvent.CELLS_MOVED, this.handleCellsMoved);

      graph.addListener(mxEvent.MOVE_CELLS, this.handleCellsMoved);
      graph.addListener(mxEvent.CELLS_RESIZED, this.handleCellsResized);

    },
    handleGraphDoubleClick(graph, event){
      window.event.preventDefault();
      window.event.stopPropagation();
      //// graphUtil.getMainCell();
      const cell = event.properties.cell;
      //// const mainCell = graphUtil.getMainCell(cell);      
      const graphNode = cell.umlShape.graphNode;
      const typeConf = app.activeProject.configManager.getTypeNameConfigItem(graphNode.getTypeName());
      if (!typeConf) return;
      const canOpenTypes = [GraphNodeType.DIAGRAM, GraphNodeType.MATRIX, GraphNodeType.TABLE, GraphNodeType.RELATION_VIEW];
      console.log(typeConf.type);
      if (canOpenTypes.includes(typeConf.type)){
        actions.getAction('openDiagram').run(graphNode);

      } 
    },
    handleGraphClick(graph, event) {
      this.handleCloseContextMenu(graph, event);
      if (window.event.button === 2) {
        this.handleRightClick(graph, event);
      } else {
        this.handleLeftClick(graph, event);
      }
    },
    handleRightClick(graph, evt) {

      window.event.preventDefault();
      window.event.stopPropagation();
      let cell = evt.properties && evt.properties.cell;
      if (!cell) return;
      dumpLog('right click ', cell);
      console.log('right click ', cell);
      
      //// window.debugUtil.clickedCell = cell;
      //// window.debugUtil.graph = graph;
      const mainShape = graphUtil.getMainShape(cell.umlShape);

      if (cell) {
        const behavior = behaviors.get(mainShape.getTypeName());
        const menuItems =  behavior.getContextMenu(mainShape, this.diagramNode);
        if (!(menuItems && menuItems.length)) return;
        dumpLog('menuItems', menuItems);
        ContextMenu.popup({
          x: window.event.clientX,
          y: window.event.clientY,
          menuItems,
          getPortal: (i) => this.contextMenu = i
        }).then(menuItem => {
          dumpLog(this.graph, mainShape, menuItem, menuItem.value, 'onShapeMenuClick');
          actions.getAction(menuItem.value).onShapeMenuClick(this.graph, mainShape, menuItem);

        }).finally(() => {
          this.contextMenu = undefined;
        });
      }

    },
    handleLeftClick(graph, event) {
      this.handleChangeSelectedElement(graph, event);
      this.handleShowPopMenu(graph, event); //// 情景菜单 bar
      this.startEdit(graph, event);
      this.handleSiderBarItemDrop(graph, event);
      this.handleShowVertexBtn(graph, event);
      this.handleShowPropertyMenu(graph, event); //// 添加属性的菜单
      this.handleEdgeClick(graph, event);

    },

    handleShowPopMenu(graph, event) {
      this.$nextTick(() => {
        const selection = graph.getSelectionModel();
        const cell =  event.properties.cell;
        const selectedCell =  selection.cells[0];
        const mainCell = graphUtil.getMainCell(cell);
        const isEdge = selectedCell && selectedCell.edge;
        const inConnect = graph.connectionHandler.previous;
        //// console.log(mainCell, mainCell === selectedCell, !inConnect, mainCell.deep !== 0);
        //// 依据需求，线也可以有弹窗
        if (mainCell && mainCell === selectedCell && !inConnect && mainCell.umlShape.owner) {

          this.setPopMenuVisible(true, mainCell, event, mainCell.umlShape.getTypeName());

        } else {
          this.setPopMenuVisible(false);

        }
      });

    },
    /**
     * siderBar上的drop处理，分为元素drop和连线drop
     */
    handleSiderBarItemDrop(graph, event) {
      if (!this.$refs.siderBar.dragData) return;
      const dragData = this.$refs.siderBar.dragData;
      const typeName = this.$refs.siderBar.dragData.typeName;
      const typeNameConfigItem = app.configManager.getTypeNameConfigItem(typeName);

      const isEdge = typeNameConfigItem && typeNameConfigItem.type === 'edge';
      this.graph.visible = true;
      dumpLog('dragData----------', dragData, isEdge);
      let targetCell =  graphUtil.getMainCell(event.properties && event.properties.cell);
      if (typeName === 'ItemFlow' && this.diagramType === 'SysMLInternalBlockDiagram'){
        if (["BindingConnector", "Connector"].includes(targetCell.umlShape.getTypeName())){
          console.log('弹框');
          actions.getAction(typeName).run(targetCell);
          //// treeUtil.showWizardAttribute();
          
        }
        this.$refs.siderBar.dragData = undefined;
        graph.highlight.hide();
        return;
      }

      //// 如果dragData中有graphNode则走treeDrop的逻辑（例如. metaclass是选一个class然后拖入）
      if (dragData.graphNode){
        const behavior = behaviors.get(targetCell.umlShape.getTypeName());
        const relativePoint = graphUtil.getRelativePoint(graph, targetCell, window.event.clientX, window.event.clientY);

        try {
          behavior.treeDrop(dragData.graphNode, targetCell.umlShape, relativePoint, this.diagramNode);
        } finally {
          this.$refs.siderBar.dragData = undefined;
          this.graph.highlight.hide();

        }
        return;

      }
      //// const directMessage = ["LostMessage", "FoundMessage", "MessageToSelf", "RecursiveMessage"];
      if (isEdge) { //// 如果是edge并且不是时序图的可直接drop的message
        const source = targetCell;
        if (source) {
          //// 检查cell 是否可以作为连线的起点
          const  behavior = behaviors.get(source.umlShape.getTypeName());
          let valid;
          //// 画布不能连线
          if (source.umlShape.owner == null){
            valid = false;
          } else {
            const relativePoint = graphUtil.getRelativePoint(graph, source, window.event.clientX, window.event.clientY);
            valid = behavior.beforeStartConnect(typeName, source.umlShape, relativePoint, this.diagramNode);
            if (valid){
              valid = behavior.isValidConnectSource( typeName, source.umlShape, this.diagramNode);
            }

          }
          if (!valid) {
            graph.highlight.hide();
            this.$refs.siderBar.dragData = undefined;
            return;
          }

          this.startConnect(source, event, typeName); //// 后续创建edge的过程mxConnectionHandler的createEdge中
        }
        ////
      //// 如果是port
      }  else { //// 如果是cell，
       
        if (!targetCell){
          targetCell = this.graphModel.cells[this.graph.diagramShape.id];
        }
        targetCell = GraphShapeUtil.getInteractCell(typeName, targetCell);

        const relativePoint = graphUtil.getRelativePoint(graph, targetCell, window.event.clientX, window.event.clientY);
        const behavior = behaviors.get(targetCell.umlShape.getTypeName());
        dumpLog(behavior);
       
        const newShape = behavior.siderBarItemDrop(typeName, targetCell.umlShape, relativePoint, this.diagramNode)[0];
        if (typeName === 'Instance') { //// 组件区的实例拖放至画布时弹窗
          const graphNode = newShape.container;
          this.showClassifierSelectModal(graphNode, newShape);
        }
      }

      this.$refs.siderBar.dragData = undefined;
      graph.highlight.hide();

    },

    //// 选择分类弹窗
    showClassifierSelectModal(graphNode, shape) {
      GraphNodeUtil.initProperty(graphNode);
      const properties = graphNode.properties.toArray();
      const classifierProperty = properties.filter(item => item.config.key === 'Classifier')[0];
      const classifiers = PropertyUtil.getNodePropertyValue(graphNode, 'Classifier');
      const currentIds = classifiers.map(item => item.id);
      dumpLog('setProperty', { classifierProperty, graphNode, currentIds, shape });
      actions.getAction('setProperty').run(classifierProperty, graphNode, currentIds, shape);
    },

    handleSelectCell(id){
      this.$nextTick(() => {
        if (!this.visible) return;
        const toSelectCell = this.graphModel.cells[id];
        toSelectCell && this.graph.getSelectionModel().setCell(toSelectCell);

      });

    },
    handleFreshGraph(option = { all: true }) {
      console.log('fresh-start');
      freshUtil.freshGraph(this.graph, option);
    },
    handleTreeDrop(event) {
      if (!treeUtil.dragData) return;
      window.event.stopPropagation();
      window.event.preventDefault();

      dumpLog('tree-drop', treeUtil.dragData);
      const graphNode = treeUtil.dragData;

      const graph = this.ui.editor.graph;
      const model = treeUtil.dragData.model;

      const typeName = graphNode.getTypeName();

      const point = shapeApi.getRelativePointForTree(this.graph, this.graph.model.cells[1], event.clientX, event.clientY,);
      const hoverCell = this.graph.getCellAt(	point.x, point.y);
      let targetCell = graphUtil.getMainCell(hoverCell);
      //// console.log('point', point, hoverCell, targetCell);
      if (!targetCell) {
        targetCell = this.graphModel.cells[this.graph.diagramShape.id];
      }
      targetCell =  GraphShapeUtil.getInteractCell(graphNode.getTypeName(), targetCell);
      const behavior = behaviors.get(targetCell.umlShape.getTypeName());
      const relativePoint = graphUtil.getRelativePoint(graph, targetCell, window.event.clientX, window.event.clientY);
      console.log('relativePoint', relativePoint);
      try {
        this.graph.visible = true;
        behavior.treeDrop(graphNode, targetCell.umlShape, relativePoint, this.diagramNode);
      } finally {
        this.graph.highlight.hide();

      }
    },

    setHighlight(cell, valid){
      const graph = this.graph;
      graph.highlight.setHighlightColor(valid ? graph.highlight.validColor : graph.highlight.invalidColor);
      graph.highlight.highlight(graph.view.getState(cell));

    },

    handleSiderBarHighlight(graph, evt) {
      const dragData = this.$refs.siderBar.dragData;
      if (!dragData) return;
      const cell = evt.state && evt.state.cell;
      if (!cell) return;
      let targetCell = graphUtil.getMainCell(cell);
      const behavior = behaviors.get(targetCell.umlShape.getTypeName());
      let valid = false;
      const item = app.activeProject.configManager.getTypeNameConfigItem(dragData.typeName);
      if (item.type === 'edge'){
        valid =  behavior.isValidConnectSource(dragData.typeName, targetCell.umlShape,  this.diagramNode);

      } else {
        const graphNodeTypeName = dragData.typeName;
        targetCell = GraphShapeUtil.getInteractCell(graphNodeTypeName, targetCell);
 
        valid = behavior.siderBarItemHover(dragData.typeName, targetCell.umlShape, this.diagramNode);

      }
      //// const valid = behavior.siderBarItemHover(dragData.typeName, targetCell.umlShape, this.diagramNode);
      this.setHighlight(targetCell, valid);

    },
    handleMouseMovePopMenuVisiable(graph, evt) {
      const selection = graph.getSelectionModel();
      const hoverCell =  evt.state && evt.state.cell;
      const selectedCell =  selection.cells[0];
      const mainCell = graphUtil.getMainCell(hoverCell);
      const isEdge = selectedCell && selectedCell.edge;
      const inConnect = graph.connectionHandler.previous;

      if (mainCell && mainCell === selectedCell && !isEdge && !inConnect && mainCell.deep !== 0) {

        //// console.log(selection.cells[0], '----------');
        if (this.popMenu.closeTimer) {
          clearTimeout(this.popMenu.closeTimer);
          this.popMenu.closeTimer = undefined;
        }
        //// console.log('setPopMenuVisible', true, mainCell);
        this.setPopMenuVisible(
          true,
          mainCell,
          evt,
          mainCell.umlShape.getTypeName()
        );
      } else {
        if (this.popMenu.visible === true && !this.popMenu.closeTimer) {
          this.popMenu.closeTimer = setTimeout(() => {
            this.popMenu.closeTimer = undefined;
            this.setPopMenuVisible(false);
          }, 500);

        }

      }

    },
    handleLabelChange(graph, event) {
      //// console.warn(); ('label change', event);
      //// const cell = event.properties && event.properties.cell;
      //// if (!cell) return;
      //// if (cell.umlShape && cell.umlShape.getTypeName() === 'LifeLine'){
      ////   LifelineUtil.updateAll(this.diagramNode.umlDiagramFrame);
      ////   this.$bus.emit('fresh-graph');
      //// }
    },

    //// 如果点击了画布则关闭contextmenu
    handleCloseContextMenu(graph, mxevent) {
      if (!mxevent.properties) return;
      const { cell, event } = mxevent.properties;
      if (window.event.button === 0 || (cell && cell.deep === 0)) {
        if (this.contextMenu) {
          this.contextMenu.removeImmediately();
          this.contextMenu = undefined;

        }
      }

    },

    handleEdgeClick(graph, mxevent) {
      if (!mxevent.properties) return;
      const { cell, event } = mxevent.properties;

      if (cell && cell.edge) {
        console.log('edg click');
      }

    },

    /**
     * 显示cell上的icon按钮
     */
    handleShowVertexBtn(graph, event) {
      console.log('handleShowVertexBtn');

      //// graphUtil.getMainCell();
      const cell = event.properties.cell;

      //// 如果点击的是按钮
      if ( cell && this.vertexBtn.btns.find(shape => shape.id === cell.id)){
        return;
      }

      const mainCell = graphUtil.getMainCell(cell);
      if (mainCell && mainCell.umlShape.container.disabeld === true) return;
      if (mainCell && this.vertexBtn.parentCellId === mainCell.id ) return;

      //// 先删除 已有的btn
      let needFresh = false;

      if (this.vertexBtn.btns.length) {
        this.clearVertexBtn();
        needFresh = true;

      }

      //// 找出btnShape
      if (mainCell && mainCell.umlShape && mainCell.umlShape !== graph.diagramShape) {
        const behavior = behaviors.get(mainCell.umlShape.getTypeName());

        if (behavior.hasAddIcon(mainCell.umlShape, this.diagramNode)) {
          mainCell.umlShape.btnShapes = [propertyMenuUtil.createAddIconShape(mainCell.umlShape)];
          mainCell.umlShape.btnShapes.forEach(i => {
            //// TODO
            resizeUtil.initShape(i);
          });
          this.vertexBtn.btns = mainCell.umlShape.btnShapes;
          this.vertexBtn.parentCellId = mainCell.id;

          needFresh = true;
        }
      }

      needFresh && this.$bus.emit('fresh-graph');

    },
    clearVertexBtn(){
      const parentCell =  this.graphModel.cells[ this.vertexBtn.parentCellId];
      if (!parentCell) return;
      parentCell.umlShape.btnShapes = [];

      this.vertexBtn.btns = [];
      this.vertexBtn.parentCellId = null;

    },
    handlePopmenuMouseEnter() {
      if (this.popMenu.closeTimer) {
        clearTimeout(this.popMenu.closeTimer);
        this.popMenu.closeTimer = undefined;
      }

    },
    /**
     * 点击cell上的加号按钮（添加属性按钮）,弹出下拉选择菜单
     */
    handleShowPropertyMenu(graph, event) {

      const cell = event.properties.cell;
      if (!cell || !cell.umlShape || cell.umlShape.shapeType !== 'addIcon') return;
      const behavior = behaviors.get(cell.umlShape.owner.getTypeName());
      const ok = behavior.onClickAddIcon(cell.umlShape.owner, this.diagramNode);
      if (ok === false) return;
      const menuItems = behavior.getPropertyMenu(cell.umlShape.owner, this.diagramNode);
      if (!menuItems) return;
      window.event.stopPropagation();
      ContextMenu.popup({
        x: window.event.clientX,
        y: window.event.clientY,
        menuItems,
        getPortal: (i) => this.contextMenu = i
      }).then(res => {
        console.log(res);
        dumpLog('addProperty', this.graph, cell.umlShape.owner, res);
        actions.getAction('addProperty').onShapeMenuClick(this.graph, cell.umlShape.owner, res);

      }).finally(() => {
        this.contextMenu = undefined;
      });

    },

    /**
     * 点击cell时直接编辑
     */
    startEdit(graph, event) {
      const cell = event.properties && event.properties.cell;
      const umlShape = cell && cell.umlShape;

      const state = graph.view.getState(cell);
      if (!state) return;
      if (window.app.ui.isConnectEvent){
        app.ui.isConnectEvent = false;
        console.log('连接事件结束时不用开启编辑');
        return;
      }
      if (treeUtil.dragData || this.$refs.siderBar.dragData) {
        return;

      }
      if (state.style.editable !== 0 && state.cell.style.startsWith('text;') && graph.cellEditor.editingCell !== cell){
        graph.startEditingAtCell(cell, event );

      }

    },
    setPopMenuVisible(visible, cell, mxevent, typeName) {
      //// console.log(cell);
      if (visible && cell?.umlShape?.container?.disabled === true) return;
      this.popMenu.visible = visible;
      const graph = this.ui.editor.graph;
      if (!visible) return;
      if (this.popMenu.closeTimer){
        clearTimeout(this.popMenu.closeTimer);
        this.popMenu.closeTimer = undefined;
      }
      const bounds = cell.geometry;
      const cellState = this.graph.view.getState(cell);
      const x = cellState.x;
      const y = cellState.y;

      const graphCon = this.$refs.graphDom.querySelector('.geDiagramContainer');
      this.popMenu.x = (x + cellState.width   + 20 )  - graphCon.scrollLeft;

      this.popMenu.y = (y  - 20 )  - graphCon.scrollTop; //// y向上偏移20
      this.popMenu.mxEvent = mxevent;
      this.popMenu.cellId = cell.id;
      let type = null;
      const graphNode = cell.umlShape.graphNode;

      this.popMenu.menuItems = behaviors.get(typeName).getPopMenu(cell.umlShape, this.diagramNode);

    },
    handlePopMenuClick(item) {
      dumpLog(item);
      const graph = this.ui.editor.graph;
      const selection = graph.getSelectionModel();
      this.setPopMenuVisible(false);
      const sourceCell = selection.cells[0];
      if (item.value === 'ItemFlow') {
        console.log('itemflow 单独的交互');
        actions.getAction(item.value).onPopupMenuClick(graph, item);
      } else  if (item.type === 'edge') {
        const  behavior = behaviors.get(sourceCell.umlShape.getTypeName());
        let valid;
        //// 画布不能连线
        if (sourceCell.umlShape.owner == null){
          valid = false;
        } else {
          if (item.value === 'LostMessage' || item.value === 'FoundMessage'){
            this.$refs.siderBar.setTypeNameInDrag(item.value);
            return;
          }
          const relativePoint = graphUtil.getRelativePoint(graph, sourceCell, window.event.clientX, window.event.clientY);

          valid = behavior.beforeStartConnect(item.value, sourceCell.umlShape, null, this.diagramNode);
          if (valid){
            valid = behavior.isValidConnectSource( item.value, sourceCell.umlShape, this.diagramNode);
          }
          //// console.log('valid', valid);

        }
        //// const valid = behaviors.get(sourceCell.umlShape.getTypeName()).isValidConnectSource(item.value, sourceCell.umlShape, this.diagramNode);
        valid && this.startConnect(sourceCell, this.popMenu.mxEvent, item.value);
      } else if (item.value === 'showConstraintParameter') {
        this.showConstraintParameter(sourceCell.umlShape);
      } else if (item.value === 'setClassifier') {
        this.showClassifierSelectModal(sourceCell.umlShape.container, sourceCell.umlShape);
      } else if (item.type === 'port') {
        const behavior = behaviors.get(sourceCell.umlShape.getTypeName());
        const point = GraphNodeUtil.getPopmenuPortPosition(sourceCell.umlShape);
        console.log(point);
        behavior.siderBarItemDrop(item.value, sourceCell.umlShape, point, this.diagramNode);

        //// actions.getAction(item.value).onShapeMenuClick(this.graph, sourceCell.umlShape, item);
      } else if (item.type === 'action') {
        actions.getAction(item.value).onPopupMenuClick(graph, item);
      } else if (item.type === 'setType') {
        const currentGraphNode = sourceCell.umlShape.graphNode;
        const typeProperty = PropertyUtil.getNodePropertyValue(currentGraphNode, 'Type');
        const allowedTypes = this.getFilterCondtion(sourceCell.umlShape.getTypeName());
        selectValue.popup({
          currentId: typeProperty && typeProperty.id,
          isNodeSelectable: (node) => {
            return allowedTypes.includes(node.getTypeName());
          }
        }).then(res => {
          if (currentGraphNode.getTypeName() === 'InteractionUse'){
            PropertyUtil.updateProperty(currentGraphNode, 'RefersTo', [res.graphNode]);
          } else {
            PropertyUtil.updateProperty(currentGraphNode, 'Type', [res.graphNode]);

          }
          currentGraphNode.childLeafElement.forEach(shape => {
            const main = GraphShapeUtil.getAbsoluteMainShape(shape);
            resizeUtil.updateAutoFlow(main);

          });
          //// GraphShapeUtil.getAbsoluteMainShape(sourceCell.umlShape)
          //// ResizeUtil.updateAutoFlow()
          this.$bus.emit('fresh-graph');
        }) .catch((e) => {
          e && console.error(e);
        });

      }
    },

    //// 显示约束参数
    showConstraintParameter(shape) {
      //// 如果存在关联类型
      const linkedType = PropertyUtil.getNodePropertyValue(shape.graphNode, 'Type');
      //// console.log('linkedType', linkedType, shape,);
      const childrenIdObj = {}; //// 存取已存在的children的id
      if (shape.children) { 
        shape.children.forEach(child => {
          childrenIdObj[child.graphNode.id] = true;
        });
      }
      if (linkedType) {
        const blockNode = graphHelper.getGraphNodeById(linkedType.id, app.activeProjectId);
        if (blockNode.children) {
          blockNode.children.forEach(graphNode => {
            //// console.log('graphNode', graphNode, graphNode.getTypeName());
            const currentType = graphNode.getTypeName();
            if (currentType === 'ConstraintParameter' && !childrenIdObj[graphNode.id]) {             
              const position = GraphNodeUtil.getPopmenuPortPosition(shape);
              shape.box.boxHeight = Math.max(shape.box.boxHeight, position.y + 20);
              resizeUtil.updateBoundsByBox(shape);
              const newShape = shape.addChildShape(currentType, graphNode, shape.owner, shape)[0];
              resizeUtil.initShape(newShape, position.x, position.y);
            }
          });
        }
        //// console.log('blockNode', blockNode, shape);
        app.$bus.emit('fresh-graph');
      }
    },

    //// 获取需要过滤的条件数组
    getFilterCondtion(type) {
      let arr = [];
      switch (type) {
        case 'PartProperty':
          arr = ['Block', 'InterfaceBlock'];
          break;
        case 'ConstraintProperty':
          arr = ['ConstraintBlock'];
          break;
        case 'FlowProperty':
          arr = ['ValueType', 'Block', 'Signal'];
          break;
        case 'ValueProperty':
          arr = ['ValueType', 'Enumeration'];
          break;
        case 'ReferenceProperty':
          arr = ['Block'];
          break;
        case 'ProxyPort':
          arr = ['InterfaceBlock'];
          break;
        case 'FullPort':
          arr = ['Block'];
          break;
        case 'FlowPort':
          arr = ['ValueType', 'Block', 'Signal', 'FlowSpecification'];
          break;
        case 'ConstraintParameter':
          arr = ['Block', 'ValueType'];
          break;
        case 'DistributedProperty':
          arr = ['Block', 'ValueType'];
          break;
        case 'InteractionUse':
          arr = ['Interaction'];
          break;
      }
      return arr;
    },

    startConnect(cell, event, edgType) {
      const graph = this.ui.editor.graph;
      const state = graph.view.getState(cell);
      app.ui.curEdgeType = edgType;
      const relativePoint = graphUtil.getRelativePoint(graph, this.graphModel.getCell(this.graph.diagramShape.id), window.event.clientX, window.event.clientY);
      const valid = behaviors.get(cell.umlShape.getTypeName()).isValidConnectSource(edgType, cell.umlShape, this.diagramNode);
      if (!valid) return;
      graph.connectionHandler.start(
        state,
        window.event.clientX,
        window.event.clientY,

      );
    },
    handleCellsMoved(graph, event) {
      if (graph.inFresh) return;
      const { cells, dx, dy } = event.properties;
      dumpLog('handleCellsMoved', event);
      cells.forEach(cell => {
        this.updateGeo(cell, { dx, dy, isMove: true });

      });
      this.$bus.emit('fresh-graph-debounce');

      this.$nextTick(() => {
        const sortedParentIds = [];
        cells.forEach(cell => {
          if (cell.parent == null || sortedParentIds.includes(cell.parent.id)) return; //// 拖动后触发删除会引起parent没有
          const orderedCells = shapeApi.sortBySize(cell.parent.children);
          //// if (cell.parent.umlShape.getTypeName() === 'Lifeline'){
          ////   orderedCells.reverse();
          //// }
          this.graph.orderCells(false, orderedCells);
          sortedParentIds.push(cell.parent.id);

        });

      });
    },
    handleCellsResized(graph, event) {
      if (graph.inFresh) return;
      const { cells } = event.properties;
      dumpLog('handleCellsResized', event);
      this.updateGeo(cells[0], { isResize: true });
      this.$bus.emit('fresh-graph');

      this.$nextTick(() => {
        const orderedCells = shapeApi.sortBySize(cells[0].parent.children);
        this.graph.orderCells(false, orderedCells);
      });

    },

    updateGeo(cell, { dx, dy, isMove, isResize }) {

      const { x, y, width, height, offset } = cell.geometry;
      const diagram = this.graph.diagram;
      const oldShape = cell.umlShape;
      const oldBounds = cell.umlShape.bounds;
      let bounds = undefined;
      console.log('update geo');
      //// cell.deep为3是messagetoself等
      //// if (lifelineUtil.isMessageEdge({ typeName: cell.typeName }) && (cell.deep === 1 || cell.deep === 3)) {
      ////   console.log('message拖动处理');
      ////   lifelineUtil.reArrangeLifelineMessage(this.graph, cell, dx, dy);
      ////   //// graphUtil.expandParentSize(cell.umlShape);
      ////   graphUtil.expandParentSize(lifelineUtil.getLifelineShapeInGraph(this.graph));

      ////   return;
      //// }
      if (!oldBounds) return; //// 批量移动时，edge没有bounds
      //// debugger;

      if (offset && (offset.x !== oldBounds.offset.x || offset.y !== oldBounds.offset.y)) { //// edgeLabel拖动时只有offset变化
        bounds = { x, y, width, height, offset: { x: offset.x, y: offset.y } };
      } else if (x !== oldBounds.x || y !== oldBounds.y || width !== oldBounds.width || height !== oldBounds.height) {
        bounds = { x, y, width, height, };
      } else if (cell.edge && cell.geometry.sourcePoint){ //// 悬空线的移动
        bounds = {
          x: 0,
          y: 0,
          width: 0,
          height: 0,
          sourcePoint: { x: cell.geometry.sourcePoint.x, y: cell.geometry.sourcePoint.y },
          targetPoint: { x: cell.geometry.targetPoint.x, y: cell.geometry.targetPoint.y },
        };
      }

      if (!bounds) return;
      const behavior =  behaviors.get(oldShape.getTypeName());
      const canMove  = behavior.beforeMove(cell.umlShape, bounds, isResize, isMove, this.diagramNode);

      if (!canMove) return;
      behavior.move(cell.umlShape, bounds, isResize, isMove, this.diagramNode);
      behavior.afterMove(cell.umlShape, isResize, isMove, this.diagramNode);

    },
    handleTreeDragMouseover(event){
      //// console.log('handleTreeDragMouseover');
      event.preventDefault();
      //// this.handleTreeDragMouseover_(event);
      if (!treeUtil.dragData) return;
      const graphNode = treeUtil.dragData;

      const point = shapeApi.getRelativePointForTree(this.graph, this.graph.model.cells[1], event.clientX, event.clientY,);
      const hoverCell = this.graph.getCellAt(	point.x, point.y);
      //// console.log('hoverCell', hoverCell);
      if (!hoverCell) {
        this.graph.highlight.hide();
        return;
      }
      let mainCell = graphUtil.getMainCell(hoverCell);
      const shapeTypeName = mainCell.umlShape.getTypeName();
      const graphNodeTypeName = graphNode.getTypeName();
      dumpLog('shapeTypeName', shapeTypeName, graphNodeTypeName);
      mainCell = GraphShapeUtil.getInteractCell(graphNodeTypeName, mainCell);
 
      const valid = behaviors.get(mainCell.umlShape.getTypeName()).treeModelHover(graphNode, mainCell.umlShape, this.diagramNode);
      event.dataTransfer.dropEffect  = valid ? 'all' : 'none';
      //// console.log('valid', valid, behaviors.get(mainCell.umlShape.getTypeName()));
      this.setHighlight(mainCell, valid);

    },

    handleChangeSelectedElement(graph, event){
      const cell = event.properties && event.properties.cell;
      const umlShape = cell && cell.umlShape;
      if (umlShape && cell.deep !== 0) {
        const graphNode = umlShape.container;
        if (graphNode) {
          app.ui.selectedElement = graphNode;

        }

      }

    },
    handleSimulateStep(record){

      controller.visitedNodeIds.size > 0 &&  controller.visitedNodeIds.forEach(id => {
        this.setNodeHighlight(id, 'green');
      });
      controller.lastVisitNodeIds.size > 0 && controller.lastVisitNodeIds.forEach(id => {
        this.setNodeHighlight(id, 'orange');
      });
      controller.activeNodeIds.size > 0 && controller.activeNodeIds.forEach(id => {
        this.setNodeHighlight(id, 'red');
      });

    },
    setNodeHighlight(nodeId, color){
      const graphNode = graphHelper.getGraphNodeById(nodeId, app.activeProjectId);
      if (!graphNode) {
        //// console.error('not found graph Node: id =', nodeId);
        return;
      }
      const shapes = graphNode.childLeafElement;////  model.getDiagramElement().toArray();
      console.log('shapes', shapes);
      //// const shapes = graphNode.childLeafElement;////  model.getDiagramElement().toArray();
      if (!this.simulateHighLightCells.get(nodeId)){
        const cells = graphNode.childLeafElement.map(shape => this.graphModel.cells[shape.id]).filter(Boolean);
        const highlits = cells.map(cell => {
          const hi = new mxCellHighlight(this.graph, color, 4, false);
          hi.opacity = 60;
          hi.highlight(this.graph.view.getState(cell));
          return hi;
        });
        this.simulateHighLightCells.set(nodeId, highlits);
      } else {
        this.simulateHighLightCells.get(nodeId).forEach(hi => {
          if (hi.highlightColor === color) return;
          hi.setHighlightColor(color);
          hi.repaint();
        });

      }

    },
    //// handleSimulateInit(){
    ////   console.log('init simulate'); 
    ////   window.controller = controller;
    ////   this.simulateHighLightCells.forEach(hi => {
    ////     ////  hi.destroy()
    ////     hi.forEach(h => {
    ////       h.destroy(); 

    ////     });
     
    ////     //// item.highLight.hide();
    ////   });
    ////   this.simulateHighLightCells = new Map();

    //// },
    handleRemoveHightlight(){
      this.graph.highlight.hide();

    },
    handleSimulateInit(option){
      dumpLog('init simulate'); 
      //// window.controller = controller;
      if (option && option.id){
        if (this.diagramNode.id !== option.id) return;
      }
      this.simulateHighLightCells.forEach(hi => {
        ////  hi.destroy()
        hi.forEach(h => {
          h.destroy(); 

        });
     
        //// item.highLight.hide();
      });
      this.simulateHighLightCells = new Map();

    },
    handleChangeNodeColor({ ids, color }){
      ids.forEach(id => {
        this.setNodeHighlight(id, color);
      });

    },
    updateShapeAutoFlow({ id, ids }){
     
      if (id){
        const cell = this.graphModel.cells[id];
        if (!cell || !cell.umlShape) return;
        if (this.updateAutoFlowTimers[id]){
          return;
        }
        //// this.updateAutoFlowTimers[id] = setTimeout(() => {
        console.log('update-shape-auto-----', id);
        resizeUtil.updateAutoFlow(cell.umlShape);
        this.updateAutoFlowTimers[id] = setTimeout(() => {
          delete this.updateAutoFlowTimers[id];
          
        }, 10);
                  
        //// }, 1);
        
      }
    }

  }
};
</script>

<style scoped>
@import './styles/grapheditor.css';
</style>
<style lang='scss'>
.v-graph-content {
  overflow: hidden;
}

.geEditor div {
  box-sizing: content-box !important;
}

.geBackgroundPage {
  border: none !important;
}
</style>
